#define _CRT_SECURE_NO_WARNINGS

#include "FiniteStateMachine.h"
#include <mmsystem.h>
#include <GL\glext.h>

FSM * FSM::Instance = NULL;

FSM::FSM()
: cam(NULL)
, frameCount(0)
, fps(0)
, currentTime(0)
, previousTime(0)
, font_style(GLUT_BITMAP_HELVETICA_18)
, frequency(30)
, angle(0.0f)
, toggle(true)
, msgboard(true)
{

}

FSM::~FSM()
{
	if (cam != NULL)
	{
		delete cam;
		cam = NULL;
	}
}

FSM* FSM::GetInstance()
{
	if (Instance == NULL)
	{
		Instance = new FSM();
	}

	return Instance;
}

void FSM::Exit()
{
	if (Instance != NULL)
	{
		delete Instance;
		Instance = NULL;
	}
}

bool FSM::LoadTGA(Texture *texture, char *filename)			// Loads A TGA File Into Memory
{
	GLubyte		TGAheader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };	// Uncompressed TGA Header
	GLubyte		TGAcompare[12];								// Used To Compare TGA Header
	GLubyte		header[6];									// First 6 Useful Bytes From The Header
	GLuint		bytesPerPixel;								// Holds Number Of Bytes Per Pixel Used In The TGA File
	GLuint		imageSize;									// Used To Store The Image Size When Setting Aside Ram
	GLuint		temp;										// Temporary Variable
	GLuint		type = GL_RGBA;								// Set The Default GL Mode To RBGA (32 BPP)

	FILE *file = fopen(filename, "rb");						// Open The TGA File

	if (file == NULL ||										// Does File Even Exist?
		fread(TGAcompare, 1, sizeof(TGAcompare), file) != sizeof(TGAcompare) ||	// Are There 12 Bytes To Read?
		memcmp(TGAheader, TGAcompare, sizeof(TGAheader)) != 0 ||	// Does The Header Match What We Want?
		fread(header, 1, sizeof(header), file) != sizeof(header))				// If So Read Next 6 Header Bytes
	{
		if (file == NULL)									// Did The File Even Exist? *Added Jim Strong*
			return false;									// Return False
		else
		{
			fclose(file);									// If Anything Failed, Close The File
			return false;									// Return False
		}
	}

	texture->width = header[1] * 256 + header[0];			// Determine The TGA Width	(highbyte*256+lowbyte)
	texture->height = header[3] * 256 + header[2];			// Determine The TGA Height	(highbyte*256+lowbyte)

	if (texture->width <= 0 ||								// Is The Width Less Than Or Equal To Zero
		texture->height <= 0 ||								// Is The Height Less Than Or Equal To Zero
		(header[4] != 24 && header[4] != 32))					// Is The TGA 24 or 32 Bit?
	{
		fclose(file);										// If Anything Failed, Close The File
		return false;										// Return False
	}

	texture->bpp = header[4];							// Grab The TGA's Bits Per Pixel (24 or 32)
	bytesPerPixel = texture->bpp / 8;						// Divide By 8 To Get The Bytes Per Pixel
	imageSize = texture->width*texture->height*bytesPerPixel;	// Calculate The Memory Required For The TGA Data

	texture->imageData = (GLubyte *)malloc(imageSize);		// Reserve Memory To Hold The TGA Data

	if (texture->imageData == NULL ||							// Does The Storage Memory Exist?
		fread(texture->imageData, 1, imageSize, file) != imageSize)	// Does The Image Size Match The Memory Reserved?
	{
		if (texture->imageData != NULL)						// Was Image Data Loaded
			free(texture->imageData);						// If So, Release The Image Data

		fclose(file);										// Close The File
		return false;										// Return False
	}

	for (GLuint i = 0; i < int(imageSize); i += bytesPerPixel)		// Loop Through The Image Data
	{														// Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
		temp = texture->imageData[i];							// Temporarily Store The Value At Image Data 'i'
		texture->imageData[i] = texture->imageData[i + 2];	// Set The 1st Byte To The Value Of The 3rd Byte
		texture->imageData[i + 2] = temp;					// Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
	}

	fclose(file);											// Close The File

	// Build A Texture From The Data
	glGenTextures(1, &texture[0].texID);					// Generate OpenGL texture IDs

	glBindTexture(GL_TEXTURE_2D, texture[0].texID);			// Bind Our Texture
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear Filtered
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Filtered

	if (texture[0].bpp == 24)									// Was The TGA 24 Bits
	{
		type = GL_RGB;										// If So Set The 'type' To GL_RGB
	}

	glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

	return true;											// Texture Building Went Ok, Return True
}

bool FSM::Init()
{
	Math::InitRNG();

	cam = new Camera(Camera::LAND_CAM);
	cam->SetPosition(0.0, 2.0, -5.0);
	cam->SetDirection(0.0, 0.0, 1.0);

	glEnable(GL_TEXTURE_2D);
	// Insert texture here
	if (!LoadTGA(&floor[0], "bin/floor.tga"))
		return false;
	glDisable(GL_TEXTURE_2D);

	CUnit *RangeUnit = FetchRangeUnit(rand() % 2 + 1);
	RangeUnit->SetPos(Vector3D(rand() % 600 + 100, rand() % 400 + 100 ,0));
	UnitList.push_back(RangeUnit);

	CUnit *MeleeUnit = FetchMeleeUnit(rand() % 2 + 1);
	MeleeUnit->SetPos(Vector3D(rand() % 600 + 100, rand() % 400 + 100 ,0));
	UnitList.push_back(MeleeUnit);

	MessageBoard* message = new MessageBoard();
	message->SetMessage(MeleeUnit, "NULL", "NULL");
	MeleeUnit->SetMessage(message);
	Msgb.push_back(message);

	MessageBoard *message2 = new MessageBoard();
	message2->SetMessage(RangeUnit, "NULL", "NULL");
	RangeUnit->SetMessage(message2);
	Msgb.push_back(message2);

	return true;
}

void FSM::Update()
{
	static int frame = 0;
	static int lastTime = GetTickCount();
	++frame;
	int time = GetTickCount();
	float dt = (time - lastTime) / 1000.f;
	lastTime = time;

	for(int it = 0; it < UnitList.size(); ++it)
	//for (std::vector<CUnit*>::iterator it = UnitList.begin(); it != UnitList.end(); it++)
	{
		CUnit* go_Unit = UnitList[it];

		if (go_Unit->GetActive())
		{
			for (int it2 = 0; it2 < UnitList.size(); ++it2)
			{
				CUnit* go_Unit2 = UnitList[it2];

				if(go_Unit2->GetActive() && it != it2)
				{
					go_Unit->UnitInteraction(go_Unit2);
				}
			}

			if (go_Unit->GetHealth() > 0)
			{
				go_Unit->Update(dt);
				MessageBoardUpdate(go_Unit);
			}
			else
			{
				go_Unit->SetActive(false);
				for (int itr = 0; itr < Msgb.size(); itr++)
				{
					if (Msgb[itr] == go_Unit->GetCurrentMsg())
					{
						delete Msgb[itr];
						Msgb.erase(Msgb.begin() + itr);
						break;
					}
				}
			}

			if(CArrow* test = dynamic_cast<CArrow*>(go_Unit)) 
			{
				if(test->GetPos().x > 800 || test->GetPos().x < 0 ||  test->GetPos().y > 600 || test->GetPos().y <0)
					test->SetActive(false);
			}
		}
		go_Unit->SetToggle(toggle);

	}

	for (int i = 0; i < Msgb.size(); i++)
	{
		if(Msgb[i]->Getto() == NULL|| !Msgb[i]->Getto()->GetActive())
			Msgb[i]->SetRecv(NULL);
	}
}

void FSM::renderScene()
{
	// Clear the buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	cam->Update();

	// Enable 2D text display and HUD
	cam->SetHUD(true);

	DrawEnvironment();

	if (msgboard)
	{
		MessageBoardPrint();
	}
	
	if (toggle)
	{
		RenderLegend(600, 24);
	}

	cam->SetHUD(false);

	// Flush off any entity which is not drawn yet, so that we maintain the frame rate.
	glFlush();

	// swapping the buffers causes the rendering above to be shown
	glutSwapBuffers();
	glutPostRedisplay ();
}

void FSM::changeSize(int w, int h) {

	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	float ar = 4.0f / 3.0f;

	int viewW = w;
	int viewH = w / ar;
	if (viewH > h)
	{
		viewH = h;
		viewW = h * ar;
	}

	int vpX = (w - viewW) / 2;
	int vpY = (h - viewH) / 2;

	// Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Set the viewport to be the entire window
	glViewport(vpX, vpY, viewW, viewH);

	// Set the correct perspective.
	gluPerspective(45, ar, 1, 1000);
	glMatrixMode(GL_MODELVIEW);
}

void FSM::inputKey(int key, int x, int y) {

	switch (key) {
	case GLUT_KEY_LEFT:
		break;
	case GLUT_KEY_RIGHT:
		break;
	case GLUT_KEY_UP:
		break;
	case GLUT_KEY_DOWN:
		break;
	}
}

void FSM::KeyboardDown(unsigned char key, int x, int y){

	myKeys[key] = true;
	switch (key)
	{
	case '1':
		toggle = !toggle;
		break;
	case 'q':
		SpawnNewMelee(1);
		break;
	case 'w':
		SpawnNewMelee(2);
		break;
	case 'a':
		SpawnNewRange(1);
		break;
	case 's':
		SpawnNewRange(2);
		break;
	case '2':
		msgboard = !msgboard;
		break;
	case VK_ESCAPE:
		exit(0);
		break;
	}
}

void FSM::KeyboardUp(unsigned char key, int x, int y){

	myKeys[key] = false;
}

void FSM::MouseMove(int x, int y) {
}

void FSM::MouseClick(int button, int state, int x, int y) {
	switch (button) {

	case GLUT_LEFT_BUTTON:
		mouseInfo.mLButtonUp = state;
		mouseInfo.lastX = x;
		mouseInfo.lastY = y;

		break;

	case GLUT_RIGHT_BUTTON:
		break;

	case GLUT_MIDDLE_BUTTON:
		break;
	}
}

void FSM::calculateFPS()
{
	//  Increase frame count
	frameCount++;

	//  Get the number of milliseconds since glutInit called
	//  (or first call to glutGet(GLUT ELAPSED TIME)).
	currentTime = glutGet(GLUT_ELAPSED_TIME);

	//  Calculate time passed
	int timeInterval = currentTime - previousTime;

	if (timeInterval > 1000)
	{
		//  calculate the number of frames per second
		fps = frameCount / (timeInterval / 1000.0f);

		//  Set time
		previousTime = currentTime;

		//  Reset frame count
		frameCount = 0;
	}
}

void FSM::drawFPS()
{
	//  Load the identity matrix so that FPS string being drawn
	//  won't get animates
	glLoadIdentity();

	//  Print the FPS to the window
	if (fps > 28.30f)
		glColor3f(1.0f, 1.0f, 0.0f);
	else if (fps > 28.0f)
		glColor3f(1.0f, 0.0f, 1.0f);
	else
		glColor3f(1.0f, 0.0f, 0.0f);
	printw(25.0, 25.0, 0, "FPS: %4.2f", fps);
}

void FSM::printw(float x, float y, float z, char* format, ...)
{
	va_list args;	//  Variable argument list
	int len;		//	String length
	int i;			//  Iterator
	char * text;	//	Text

	//  Initialize a variable argument list
	va_start(args, format);

	//  Return the number of characters in the string referenced the list of arguments.
	//  _vscprintf doesn't count terminating '\0' (that's why +1)
	len = _vscprintf(format, args) + 1;

	//  Allocate memory for a string of the specified size
	text = (char *)malloc(len * sizeof(char));

	//  Write formatted output using a pointer to the list of arguments
	vsprintf_s(text, len, format, args);

	//  End using variable argument list 
	va_end(args);

	//  Specify the raster position for pixel operations.
	glRasterPos3f(x, y, z);


	//  Draw the characters one by one
	for (i = 0; text[i] != '\0'; i++)
		glutBitmapCharacter(font_style, text[i]);

	//  Free the allocated memory for the string
	free(text);
}

void FSM::DrawEnvironment()
{
	DrawBackground();

	for(std::vector<CUnit *>::iterator it = UnitList.begin(); it !=  UnitList.end(); ++it)
	{
		CUnit* go_Unit = *it;
		if(go_Unit->GetActive())
		{
			go_Unit->Render();
		}
		
	}
}

void FSM::DrawBackground()
{
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
		glColor3f(1, 1, 1);
		glBindTexture(GL_TEXTURE_2D, floor[0].texID);
		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);  glVertex2f(0, 0);
		glTexCoord2f(1, 0); glVertex2f(800, 0);
		glTexCoord2f(1, 1); glVertex2f(800, 600);
		glTexCoord2f(0, 1); glVertex2f(0, 600);
		glEnd();
	glPopMatrix();
	glDisable(GL_TEXTURE_2D);
}

CUnit* FSM::FetchRangeUnit(int team)
{
	CUnit* NewUnit = new CRange();
	NewUnit->SetActive(true);

	// Assign to team 1 / 2 by random
	NewUnit->SetTeam(team); 

	// Set Team Color
	if (NewUnit->GetTeam() == 1)
		NewUnit->SetColor(Vector3D(1, 0, 0));
	else if (NewUnit->GetTeam() == 2)
		NewUnit->SetColor(Vector3D(0, 1, 0));

	return NewUnit;
}

CUnit* FSM::FetchMeleeUnit(int team)
{
	CUnit* NewUnit = new CMelee();
	NewUnit->SetActive(true);

	// Assign to team 1 / 2 by random
	NewUnit->SetTeam(team); 

	// Set Team Color
	if (NewUnit->GetTeam() == 1)
		NewUnit->SetColor(Vector3D(1, 0, 0));
	else if (NewUnit->GetTeam() == 2)
		NewUnit->SetColor(Vector3D(0, 1, 0));

	return NewUnit;
}

void FSM::RenderUnit(CUnit* go_Unit)
{
	if(go_Unit->GetActive())
	{
		go_Unit->Render();
	}
}

void FSM::RenderLegend(int x, int y)
{
	glColor3f(1.0f, 1.0f, 1.0f);
	printw(x, y, 0, "LEGEND: ");
	printw(x + 10, y + 20, 0, "ATTACK RANGE");
	printw(x + 10, y + 45, 0, "DETECTION");
	printw(x + 20, y + 70, 0, "MELEE");
	printw(x + 20, y + 95, 0, "RANGE");
	printw(x, y + 120, 0, "CONTROLS: ");
	printw(x, y + 140, 0, "Q : Spawn melee unit 1");
	printw(x, y + 160, 0, "W : Spawn melee unit 2");
	printw(x, y + 180, 0, "A : Spawn range unit 1");
	printw(x, y + 200, 0, "S : Spawn range unit 2");
	glColor3f(1.0f, 0.0f, 0.0f);
	printw(x, y + 220, 0, "Red: Team 1");
	glColor3f(0.0f, 1.0f, 0.0f);
	printw(x, y + 240, 0, "Green: Team 2");

	glEnable(GL_BLEND);
	glPushMatrix();
	glTranslatef(x, y, 0);
		glPushMatrix();
			glColor4f(1.0f, 0.0f, 0.0f, 0.4f);
			glTranslatef(0, 15, 0);
			glScalef(10, 10, 1);
			glutSolidSphere(1, 20, 20);	
		glPopMatrix();
		glPushMatrix();
			glColor4f(1.0f, 1.0f, 0.0f, 0.4f);
			glTranslatef(0, 40, 0);
			glScalef(10, 10, 1);
			glutSolidSphere(1, 20, 20);
		glPopMatrix();
		glPushMatrix();
			glColor4f(0.5f, 0.0f, 0.5f, 1.0f);
			glTranslatef(0, 65, 0);
			glScalef(10, 10, 1);
				glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, CMelee::UnitTexture.texID);
			glColor3f(1.0f, 1.0f, 1.0f);
			glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex2f(-2, -2);
			glTexCoord2f(1, 0); glVertex2f(2, -2);
			glTexCoord2f(1, 1); glVertex2f(2, 2);
			glTexCoord2f(0, 1); glVertex2f(-2, 2);
		glEnd();
		glPopMatrix();
		glPushMatrix();
			glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
			glTranslatef(0, 90, 0);
			glScalef(10, 10, 1);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, CRange::UnitTexture.texID);
		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_QUADS);
		glTexCoord2f(0, 0); glVertex2f(-2, -2);
		glTexCoord2f(1, 0); glVertex2f(2, -2);
		glTexCoord2f(1, 1); glVertex2f(2, 2);
		glTexCoord2f(0, 1); glVertex2f(-2, 2);
		glEnd();
		glPopMatrix();

	glPopMatrix();
	glDisable(GL_BLEND);
}

std::vector<CUnit*>& FSM::GetUnitList(void)
{
	return UnitList;
}

void FSM::SpawnNewMelee(int team)
{
	CUnit *MeleeUnit = FetchMeleeUnit(team);
	MeleeUnit->SetPos(Vector3D(rand() % 800, rand() % 600, 0));
	UnitList.push_back(MeleeUnit);

	MessageBoard* message = new MessageBoard();
	message->SetMessage(MeleeUnit, "NULL", "NULL");
	MeleeUnit->SetMessage(message);
	Msgb.push_back(message);
}

void FSM::SpawnNewRange(int team)
{
	CUnit *RangeUnit = FetchRangeUnit(team);
	RangeUnit->SetPos(Vector3D(rand() % 800, rand() % 600, 0));
	UnitList.push_back(RangeUnit);

	MessageBoard* message = new MessageBoard();
	message->SetMessage(RangeUnit, "NULL", "NULL");
	RangeUnit->SetMessage(message);
	Msgb.push_back(message);
}

void FSM::MessageBoardUpdate(CUnit *go_Unit)
{
	if (CMelee* melee = dynamic_cast<CMelee*>(go_Unit))
	{
		//if (melee->GetHealth() <= 0)
		//{
		//	//static int msg = 0;
		//	//if (melee->GetActive())
		//	//{
		//	//	if (melee->GetHealth() <= 0 && msg != 0)
		//	//	{
		//	//		msg = 0;
		//	//		//melee->SetMessage(melee->mb, "NULL", "NULL", "NULL");
		//	//	}
		//	//	else if ((melee->GetHealth() <= 20 && melee->GetHealth() > 0) && melee->state == CMelee::State::STATE_ATTACK && msg != 2)
		//	//	{
		//	//		msg = 2;
		//	//		melee->SetMessage(melee->mb, "MELEE", "MELEE", "REPLACE ME");
		//	//		Msgb.push_back(melee->mb);
		//	//	}
		//	//	else if ((melee->GetHealth() <= 40 && melee->GetHealth() > 20) && melee->state == CMelee::State::STATE_ATTACK && msg != 1)
		//	//	{
		//	//		msg = 1;
		//	//		melee->SetMessage(melee->mb, "MELEE", "RANGE", "NEED HP");
		//	//		Msgb.push_back(melee->mb);
		//	//	}
		//	//	else
		//	//	{
		//	//		if (CMelee* melee2 = dynamic_cast<CMelee*>(go_Unit2))
		//	//		{
		//	//			if (melee2->GetMeleeCount() != melee->GetMeleeCount() && melee->GetHealth() > 0)
		//	//			{
		//	//				if (melee2->MsgTo(melee->mb) == "MELEE" && melee2->RcvMessage(melee->mb) == "REPLACE ME")
		//	//				{
		//	//					melee2->state = CMelee::State::STATE_CHASE;
		//	//				}
		//	//			}
		//	//		}
		//	//	}
		//	//}
		//	melee->SetMessage(melee->mb, "NULL", "NULL", "NULL");
		//}
		if ((melee->GetHealth() <= 20 && melee->GetHealth() > 0) && melee->state == CMelee::State::STATE_ATTACK)
		{
			melee->GetCurrentMsg()->ChangeMessage("REPLACE ME", "MELEE");
		}
		else if ((melee->GetHealth() <= 40 && melee->GetHealth() > 20) && melee->state == CMelee::State::STATE_ATTACK)
		{
			melee->GetCurrentMsg()->ChangeMessage("NEED HP", "RANGE");
		}
		else if ((melee->GetHealth() <= 60 && melee->GetHealth() > 40) && melee->state == CMelee::State::STATE_ATTACK)
		{
			melee->GetCurrentMsg()->ChangeMessage("NEED REINFORCEMENT", "MELEE");
		}
		else
		{
			melee->GetCurrentMsg()->ChangeMessage("NULL", "NULL");
			melee->GetCurrentMsg()->SetRecv(NULL);
		}
	}
	else if(CRange* range = dynamic_cast<CRange*>(go_Unit))
	{
		if (range->state == CRange::State::STATE_STUN)
		{
			range->GetCurrentMsg()->ChangeMessage("RECOVER ME", "RANGE");
		}
		else if ((range->GetHealth() <= 40 && range->GetHealth() > 0) && (range->state == CRange::State::STATE_ATTACK || range->state == CRange::State::STATE_CHARGE))
		{
			range->GetCurrentMsg()->ChangeMessage("REPLACE ME", "RANGE");
		}
		else if (range->GetShield() <= 40)
		{
			range->GetCurrentMsg()->ChangeMessage("NEED SHIELD", "MELEE");
		}
		else
		{
			range->GetCurrentMsg()->ChangeMessage("NULL", "NULL");
			range->GetCurrentMsg()->SetRecv(NULL);
		}
	}

	go_Unit->CheckMessage(Msgb);
}

void FSM::MessageBoardPrint(void)
{
	static int i = 0;

	// Messageboard background overlay
	glEnable(GL_BLEND);
	glPushMatrix();
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
	glBegin(GL_QUADS);
	glVertex2f(0, 600);
	glVertex2f(400, 600);
	glVertex2f(400, 600 - (i * 25));
	glVertex2f(0, 600 - (i * 25));
	glEnd();
	glPopMatrix();
	glDisable(GL_BLEND);

	for (i = 0; i < Msgb.size(); i++)
	{
		glPushMatrix();
		glColor3f(1.0f, 1.0f, 1.0f);
		//string msg = Msgb[i]->GetFrom() + " to " + Msgb[i]->GetTo() + ": " + Msgb[i]->RcvMessage();
		string msg;
		if(CMelee* melee = dynamic_cast<CMelee*> (Msgb[i]->Getfrom()))
		{
			msg += "Melee " + std::to_string(static_cast<long long>(melee->GetMeleeCount())) + ": ";
			msg += Msgb[i]->Getmsg() + " to " + Msgb[i]->Getsearchfor();
		}
		if (CRange* range = dynamic_cast<CRange*>(Msgb[i]->Getfrom()))
		{
			msg += "Range " + std::to_string(static_cast<long long>(range->GetRangeCount())) + ": ";
			msg += Msgb[i]->Getmsg() + " to " + Msgb[i]->Getsearchfor();
		}
		char temp[256];
		strcpy(temp, msg.c_str());
		printw(0, 600 - (i * 25), 0, temp);
		glPopMatrix();
	}

	
}